//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2011 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_UTILITY_CFIXEDPOINT_H_
#define _BUOLA_UTILITY_CFIXEDPOINT_H_

#include <buola/buola.h>
#include <buola/utility/crect.h>
#include <buola/utility/operators.h>
#include <buola/utility/cslope.h>

namespace buola {

DECLARE_NAME(SetFixed)
    
///\addtogroup utility
///@{
    
namespace detail {

template<typename tInt,int vShift>
class CFixedProduct;

/*namespace detail*/ }

/////////////////////////////////////////////////////////////////////////////////////////////////

/// fixed-point number

/// Class for storing a fixed-point number and converting from and to floating-point values.
///
/// \tparam tInt the underlying integer type
/// \tparam pShift the number of decimal bits
///
/// \todo If it seems necessary, add fixed-point arithmetic, which takes care of overflows. It
/// doesn't seem that necessary since fixed-point is not used in buola, only for freetype-related
/// stuff.
///
/////////////////////////////////////////////////////////////////////////////////////////////////
    
template<typename tInt=int,int vShift=16>
class CFixed : public ops::UComparison<CFixed<tInt,vShift>>
{
    static_assert(std::is_integral<tInt>::value,"tInt in CFixed must be of integral type");
    
    static constexpr float cShiftF=(1<<vShift);
    static constexpr double cShiftD=(1<<vShift);

    static constexpr tInt cFractionalPart=(1<<vShift)-1;

public:
    ///default constructor (uninitialized fixed-point)
    CFixed()=default;
    ///copy constructor
    CFixed(const CFixed&)=default;

    CFixed(const detail::CFixedProduct<tInt,vShift>&);

    constexpr CFixed(NSetFixed,tInt pValue)
        :   mValue(pValue)
    {}
    
    ///conversion from float (explicit)
    explicit CFixed(float pFloat)
        :   mValue(std::lrint(pFloat*cShiftF))
    {}
    
    ///conversion from double (explicit)
    explicit CFixed(double pDouble)
        :   mValue(std::lrint(pDouble*cShiftD))
    {}

    CFixed(NZero)
        :   mValue(0)
    {}
    
    ///conversion to float (explicit)
    explicit operator float() const
    {
        return float(mValue)/cShiftF;
    }
    
    ///conversion to double (explicit)
    explicit operator double() const
    {
        return double(mValue)/cShiftD;
    }
    
    ///assignment operator
    CFixed &operator=(const CFixed&)=default;

    ///equality operator
    bool operator==(const CFixed &pRH) const
    {
        return mValue==pRH.mValue;
    }

    ///less-than comparison operator
    bool operator<(const CFixed &pRH) const
    {
        return mValue<pRH.mValue;
    }

    ///addition operator
    CFixed operator+(const CFixed &pRH) const
    {
        return {nSetFixed,mValue+pRH.mValue};
    }
    
    ///subtraction operator
    CFixed operator-(const CFixed &pRH) const
    {
        return {nSetFixed,mValue-pRH.mValue};
    }

    ///negatino operator
    CFixed operator-() const
    {
        return {nSetFixed,-mValue};
    }

    ///addition-assignment operator
    CFixed &operator+=(const CFixed &pRH)
    {
        mValue+=pRH.mValue;
        return *this;
    }

    explicit operator bool() const
    {
        return mValue;
    }
    
    ///returns the fixed-point value
    tInt Get() const  {   return mValue;  }

    bool IsInteger() const {    return (mValue&cFractionalPart)==0; }
    
private:
    tInt mValue;
};

namespace detail {

template<typename tInt,int vShift>
class CFixedProduct : public ops::UComparison<CFixedProduct<tInt,vShift>>
{
public:
    CFixedProduct(const CFixed<tInt,vShift> &pLH,const CFixed<tInt,vShift> &pRH)
        :   mValue(int64_t(pLH.Get())*int64_t(pRH.Get()))
    {}

    constexpr CFixedProduct(NSetFixed,int64_t pValue)
        :   mValue(pValue)
    {}

    CFixedProduct(NZero)
        :   mValue(0)
    {}

    bool operator==(const CFixedProduct &pRH) const
    {
        return mValue==pRH.mValue;
    }
    
    bool operator<(const CFixedProduct &pRH) const
    {
        return mValue<pRH.mValue;
    }

    CFixedProduct operator+(const CFixedProduct &pRH) const
    {
        return {nSetFixed,mValue+pRH.mValue};
    }

    CFixedProduct operator-(const CFixedProduct &pRH) const
    {
        return {nSetFixed,mValue-pRH.mValue};
    }

    tInt Value() const  {   return tInt(mValue>>vShift);    }
    
private:
    int64_t mValue;
};

    
/*namespace detail*/ }

template<typename tInt,int vShift>
CFixed<tInt,vShift>::CFixed(const detail::CFixedProduct<tInt,vShift> &pProduct)
    :   mValue(pProduct.Value())
{
}

template<typename tInt,int vShift>
detail::CFixedProduct<tInt,vShift> operator*(const CFixed<tInt,vShift> &pLH,const CFixed<tInt,vShift> &pRH)
{
    return {pLH,pRH};
}

template<typename tInt,int vShift>
detail::CFixedProduct<tInt,vShift> operator*(const detail::CFixedProduct<tInt,vShift> &pLH,const CFixed<tInt,vShift> &pRH)
{
    return {pLH,pRH};
}

template<typename tInt,int vShift>
detail::CFixedProduct<tInt,vShift> operator*(const CFixed<tInt,vShift> &pLH,const detail::CFixedProduct<tInt,vShift> &pRH)
{
    return {pLH,pRH};
}

template<typename tInt,int vShift>
detail::CFixedProduct<tInt,vShift> operator*(const detail::CFixedProduct<tInt,vShift> &pLH,const detail::CFixedProduct<tInt,vShift> &pRH)
{
    return {pLH,pRH};
}

template<typename tInt,int vShift>
CFixed<tInt,vShift> operator/(const detail::CFixedProduct<tInt,vShift> &pLH,const CFixed<tInt,vShift> &pRH)
{
    return {nSetFixed,pLH.Value()/pRH.Get()};
}

template<typename tInt,int vShift>
CFixed<tInt,vShift> operator/(const detail::CFixedProduct<tInt,vShift> &pLH,const detail::CFixedProduct<tInt,vShift> &pRH)
{
    return pLH/CFixed<tInt,vShift>(pRH);
}

typedef CPoint<CFixed<>> CPoint_x;
typedef CSize<CFixed<>> CSize_x;
typedef CRect<CFixed<>> CRect_x;
typedef CSlope<CFixed<>> CSlope_x;

///@}

/*namespace buola*/ }

namespace std {

    template<typename tInt,int vShift>
    struct numeric_limits<buola::CFixed<tInt,vShift>>
    {
    private:
        typedef buola::CFixed<tInt,vShift> TType;
        typedef numeric_limits<tInt> TLimits;

    public:
        static constexpr bool is_specialized = true;

        static constexpr TType min()    {   return TType(buola::nSetFixed,TLimits::min());  }
        static constexpr TType max()    {   return TType(buola::nSetFixed,TLimits::max());  }
        static constexpr TType lowest() {   return TType(buola::nSetFixed,TLimits::min());  }

        ///\todo digits,digits10,max_digits10,radix,epsilon(),round_error(),min_exponent,min_exponent10,
        ///max_exponent,max_exponent10,has_denorm,has_denorm_loss
        
        static constexpr bool is_signed=true;
        static constexpr bool is_integer=false;
        static constexpr bool is_exact=false;

        static constexpr bool has_infinity=false;
        static constexpr bool has_quiet_NaN=false;
        static constexpr bool has_signaling_NaN=false;

        static constexpr bool is_iec559=false;
        static constexpr bool is_bounded=true;
        static constexpr bool is_modulo=false;

        static constexpr TType infinity()   {   return TType(buola::nSetFixed,0);   }
    };

/*namespace std*/ }

#endif
